# -*- coding:utf-8 -*-
# vim: set et sw=4 ts=4 sts=4 ff=unix fenc=utf8:

from uuid import uuid1

from . import utils
from .. import errors


def get_eip4vm(client, l3_network_uuid, vm_uuid):
    """

    :param client:
    :param l3_network_uuid:
    :param vm_uuid:
    :return: {'ip': str, 'uuid': str}
    """
    data = {
        'l3NetworkUuid': l3_network_uuid,
        'name': 'vip-' + str(uuid1()),
        'requiredIp': None}
    vip_uuid = client.create_vip(data).get('uuid')
    if not vip_uuid:
        raise errors.ZStackException('Get EIP for VM failed, create VIP failed')

    data = {
        'description': '',
        'name': 'eip-' + vm_uuid,
        'vipUuid': vip_uuid
    }
    eip_uuid = client.create_eip(data).get('uuid')

    if not eip_uuid:
        raise errors.ZStackException('Get EIP for VM failed, create EIP failed')

    data = {
        'conditions': [{'name': 'vmInstanceUuid',
                        'op': '=',
                        'value': vm_uuid}]
    }
    # TODO 2 and more nic
    vm_nic = client.query_vm_nic(data)
    if not vm_nic:
        raise errors.ZStackException('Get EIP for VM failed, could not found vm nic')
    else:
        vm_nic_uuid = vm_nic[0].get('uuid')

    data = {
        'eipUuid': eip_uuid,
        'vmNicUuid': vm_nic_uuid
    }
    eip_ip = client.attach_eip(data)
    client.log.info('[ZStack for OSYL] Get network [%s] EIP [%s] for VM [%s] succeed' % (
        l3_network_uuid, eip_ip.get('vipIp'), vm_uuid))
    return {'ip': eip_ip.get('vipIp'), 'uuid': eip_ip.get('uuid')}


def delete_eip_by_uuid(client, uuid):
    data = {'uuid': uuid}
    return client.delete_eip(data)


def create_virtual_router_template(client, name, management_network_uuid, router_image_uuid,
                                   zone_uuid=None, mem=256):
    if zone_uuid is None:
        zone_uuid = client.query_zone()[0].get('uuid')
    return client.create_virtual_router_offering({
        'cpuNum': 1,
        'cpuSpeed': 1,
        'description': '',
        'imageUuid': router_image_uuid,
        'isDefault': True,
        'managementNetworkUuid': management_network_uuid,
        'memorySize': mem * 1024 * 1024,
        'name': name,
        'publicNetworkUuid': management_network_uuid,
        'systemTags': ['vrouter'],
        'zoneUuid': zone_uuid
    })


def create_network(client, network_mutex, name, cidr, physical_interface, network_type, resource_uuid=None,
                   zone_uuid=None, cluster_uuid=None, services=None, vlan_start=100, vlan_stop=4000,
                   async=False):
    if network_type not in {'vrouter', 'VirtualRouter', 'Flat'}:
        raise errors.ZStackException('Create network failed, network type is wrong.')
    if network_type == 'VirtualRouter' or network_type == 'vrouter':
        if resource_uuid is None:
            raise errors.ZStackException('Create network failed, VirtualRouter uuid is None.')
    else:
        if resource_uuid:
            raise errors.ZStackException('Create network failed, Flat network give parm VirtualRouter uuid.')

    if zone_uuid is None:
        client.log.warn('Create network lack of zone uuid parm, default first zone')
        zone_uuid = client.query_zone(data={'fields': ['uuid']})[0].get('uuid')

    if cluster_uuid is None:
        client.log.warn('Create network lack of cluster uuid parm, default first cluster')
        cluster_uuid = client.query_cluster(data={'fields': ['uuid']})[0].get('uuid')

    l3_network_uuid = None
    l2_network_uuid = None
    with network_mutex:
        vlans = []
        vlans_no_l3 = []
        l2_networks_used = [net.get('l2NetworkUuid') for net in client.query_l3_network(
            data={'fields': ['l2NetworkUuid']})]
        for l2_network in client.query_l2_vlan_network(data={'fields': ['uuid', 'vlan']}):
            if l2_network.get('uuid') in l2_networks_used:
                vlans.append(l2_network.get('vlan'))
            else:
                vlans_no_l3.append(l2_network.get('vlan'))
                l2_network_uuid = l2_network.get('uuid')
        vlans.sort()
        vlans_no_l3.sort()
        vlan_max = (vlan_start if vlans == [] or vlans[-1] < vlan_start else vlans[-1]) + 1
        vlan = -1
        for i in range(vlan_start + 1, vlan_max + 1):
            if i not in vlans:
                vlan = i
                break

        if vlan > vlan_stop or vlan < 0:
            client.log.error('ERROR, VLan not enough')
            raise errors.ZStackException('ERROR, VLan not enough')

        if not vlans_no_l3:
            data = {
                'vlan': vlan,
                'physicalInterface': physical_interface,
                'zoneUuid': zone_uuid,
                # 'name': name + '-L2',
                'name': str(vlan) + '-L2',
                'type': 'L2VlanNetwork'
            }
            l2_network_uuid = client.create_l2_vlan_network(data).get('uuid')

        data = {
            'name': name,
            'type': 'L3BasicNetwork',
            'l2NetworkUuid': l2_network_uuid,
            'system': False,
            'count': False,
            'start': 0,
            'replyWithCount': True,
            'conditions': [],
        }
        l3_network = client.create_l3_network(data)
        l3_network_uuid = l3_network.get('uuid')

    if l3_network_uuid is None or l2_network_uuid is None:
        raise errors.ZStackException('Create network failed.')

    data = {
        'dns': '8.8.8.8',
        'l3NetworkUuid': l3_network_uuid
    }
    client.add_dns_to_l3_network(data, async=async)

    net_service = None
    if services is None:
        services = client.query_network_service_provider(data={
            'count': False,
            'replyWithCount': True,
            'start': 0,
            # 'fields': ['uuid', 'type']
        })
        service_uuid = None
        security_group_uuid = None
        for service in services:
            service_type = service.get('type')
            if service_type == network_type:
                service_uuid = service.get('uuid')
                net_service = service
                if security_group_uuid:
                    break
            elif service_type == 'SecurityGroup':
                security_group_uuid = service.get('uuid')
        if not all([service_uuid, security_group_uuid]):
            raise errors.ZStackException('Create network failed, network service error')
    else:
        net_service = services.get(network_type)
        service_uuid = net_service.get('uuid')
        security_group_uuid = services.get('SecurityGroup').get('uuid')

    client.attach_network_service_to_l3_network(data={
        'l3NetworkUuid': l3_network_uuid,
        'networkServices': {
            service_uuid: net_service.get('networkServiceTypes'),
            security_group_uuid: ['SecurityGroup']
        },
    }, async=async)

    data = {
        'l3NetworkUuid': l3_network_uuid,
        'name': name + '-cidr',
    }
    if type(cidr) is str:
        data['networkCidr'] = cidr
    elif type(cidr) is dict:
        data.updata(cidr)
    else:
        raise errors.ZStackException('Create network failed, cidr error')
    client.add_ip_range_by_network_cidr(data, async=async)

    if resource_uuid:
        client.create_system_tag(data={
            'resourceType': 'InstanceOfferingVO',
            'resourceUuid': resource_uuid,
            'tag': 'guestL3Network::' + l3_network_uuid,
        })

    client.attach_l2_network_to_cluster(data={
        'clusterUuid': cluster_uuid,
        'l2NetworkUuid': l2_network_uuid
    })
    client.log.info('[ZStack for OSYL]Create network with L2, L3, services succeed L3:[%s]' % l3_network_uuid)
    return l3_network_uuid


def destroy_l2_network(client, uuid, async=False):
    rsp = client.delete_l2_network({'uuid': uuid}, async=async)
    client.log.info('[ZStack for OSYL] DeleteL2Network API succeed [%s]' % client.username)
    return rsp


def destroy_l3_network(client, uuid, async=False):
    nets = client.query_l3_network({
        'conditions': [{'name': 'uuid',
                        'op': '=',
                        'value': uuid}],
        'fields': ['l2NetworkUuid']})

    if len(nets) != 1:
        raise errors.ZStackException('Query l3_network error, [%s]' % nets)
    else:
        l2_uuid = nets[0].get('l2NetworkUuid')

    return client.delete_l2_network({'uuid': l2_uuid}, async=async)


def query_security_group(client):
    return client.query_security_group()


def query_security_group_by_uuid(client, uuid):
    data = {'conditions': [{'name': 'uuid',
                            'op': '=',
                            'value': uuid}]}
    return client.query_security_group(data)


def create_security_group(client, name, description):
    data = {
        'name': name,
        'description': description
    }
    return client.create_security_group(data)


def delete_security_group(client, uuid):
    data = {'uuid': uuid}
    return client.delete_security_group(data)


def add_security_group_rule(client, security_type, protocol, start_port, end_port, security_group_uuid):
    if start_port > 65535 or end_port > 65535:
        raise errors.ZStackException('Create security group rule failed, start port or end port is bigger than 65535.')
    data = {'rules': [{'type': security_type,
                       'protocol': protocol,
                       'startPort': start_port,
                       'endPort': end_port}],
            'securityGroupUuid': security_group_uuid}
    return client.add_security_group_rule(data)


def delete_security_group_rule(client, uuid):
    data = {'ruleUuids': [uuid]}
    return client.delete_security_group_rule(data)


def query_l3_network(client, details=True):
    data = {} if details else {
        'fields': ['uuid']
    }
    return client.query_l3_network(data)


def attach_security_group_to_l3_network(client, l3_network_uuid, security_group_uuid):
    data = {
        'l3NetworkUuid': l3_network_uuid,
        'securityGroupUuid': security_group_uuid
    }
    return client.attach_security_group_to_l3_network(data)


def detach_security_group_from_l3_network(client, l3_network_uuid, security_group_uuid):
    data = {'l3NetworkUuid': l3_network_uuid,
            'securityGroupUuid': security_group_uuid}
    return client.detach_security_group_from_l3_network(data)


# TODO 遍历了三次eips，待优化
def query_eip(client):
    eips = client.query_eip()
    vm_nics_uuid = set()
    for eip in eips:
        if eip.get('vmNicUuid'):
            vm_nics_uuid.add(eip.get('vmNicUuid'))

    nic_vm_map = utils.from_nics_get_vms(client, vm_nics_uuid)
    for eip in eips:
        if eip.get('vmNicUuid'):
            eip['vm_uuid'] = nic_vm_map.get(eip.get('vmNicUuid'))
    return utils.get_resources_owner(client, eips, "EipVO")
